package nl.rug.ml.dobbelen.learning;

import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;

import nl.rug.ml.dobbelen.game.Dice;
import nl.rug.ml.dobbelen.game.DobbelenCondition;
import nl.rug.ml.dobbelen.game.DobbelenGame;

/**
 * @author Do Bich Ngoc (bichngocdo89@gmail.com)
 * 
 */
public class DobbelenState {

	private int[] histogram;
	private int[] extendedHistogram;
	private int[] index;
	private List<List<Dice>> diceGroups;

	private DobbelenState() {

	}

	public DobbelenState(List<Dice> dices) {
		histogram = DobbelenUtils.dicesToHistogram(dices);
		index = new int[7];
		diceGroups = new LinkedList<List<Dice>>();
		init(dices);
	}

	public int[] getHistogram() {
		return histogram;
	}

	public int[] getExtendedHistogram() {
		return extendedHistogram;
	}

	public int[] getIndex() {
		return index;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + Arrays.hashCode(extendedHistogram);
		result = prime * result + Arrays.hashCode(histogram);
		result = prime * result + Arrays.hashCode(index);
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		if (!(obj instanceof DobbelenState)) {
			return false;
		}
		DobbelenState other = (DobbelenState) obj;
		if (!Arrays.equals(extendedHistogram, other.extendedHistogram)) {
			return false;
		}
		if (!Arrays.equals(histogram, other.histogram)) {
			return false;
		}
		if (!Arrays.equals(index, other.index)) {
			return false;
		}
		return true;
	}

	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append('[');
		for (int i = 0; i < extendedHistogram.length; i++) {
			sb.append(extendedHistogram[i]).append(' ');
		}
		if (sb.length() > 1) {
			sb.deleteCharAt(sb.length() - 1);
		}
		sb.append(']');
		return sb.toString();
	}

	private void init(List<Dice> dices) {
		List<Integer> extendedHistogramList = new LinkedList<Integer>();

		for (int k = 0; k < histogram.length; k++) {
			index[k] = extendedHistogramList.size();
			List<Dice> nonTripletDices = new LinkedList<Dice>();
			for (Dice dice : dices) {
				if (dice.getValue() == k + 1) {
					nonTripletDices.add(dice);
				}
			}

			if (histogram[k] >= 3) {
				Map<Integer, Integer> map = new TreeMap<Integer, Integer>();
				for (Dice dice : nonTripletDices) {
					if (map.containsKey(dice.getTurn())) {
						map.put(dice.getTurn(), map.get(dice.getTurn()).intValue() + 1);
					}
					else {
						map.put(dice.getTurn(), 1);
					}
				}

				for (Entry<Integer, Integer> entry : map.entrySet()) {
					int count = entry.getValue();
					int numTripletDice = (count / 3 * 3);
					if (numTripletDice > 0) {
						extendedHistogramList.add(numTripletDice);
						List<Dice> tripletDiceGroup = new LinkedList<Dice>();
						for (Dice dice : nonTripletDices) {
							if (numTripletDice > 0) {
								tripletDiceGroup.add(dice);
								numTripletDice--;
							}
							else {
								break;
							}
						}
						diceGroups.add(tripletDiceGroup);
						nonTripletDices.removeAll(tripletDiceGroup);
					}
				}
			}
			extendedHistogramList.add(nonTripletDices.size());
			diceGroups.add(nonTripletDices);

			// Normalize
			for (int i = index[k]; i < extendedHistogramList.size() - 2; i++) {
				for (int j = i + 1; j < extendedHistogramList.size() - 1; j++) {
					if (extendedHistogramList.get(i) < extendedHistogramList.get(k)) {
						int tempHistogram = extendedHistogramList.get(i);
						extendedHistogramList.set(i, extendedHistogramList.get(j));
						extendedHistogramList.set(j, tempHistogram);
						List<Dice> tempGroup = diceGroups.get(i);
						diceGroups.set(i, diceGroups.get(j));
						diceGroups.set(j, tempGroup);
					}
				}
			}
		}

		// Convert to arrays
		extendedHistogram = new int[extendedHistogramList.size()];
		for (int i = 0; i < extendedHistogram.length; i++) {
			extendedHistogram[i] = extendedHistogramList.get(i);
		}
		index[index.length - 1] = extendedHistogram.length;
	}

	public List<DobbelenAction> generateActions() {
		if (DobbelenCondition.mustThrowAll(this)) {
			List<DobbelenAction> actions = new LinkedList<DobbelenAction>();
			actions.add(new DobbelenAction(new int[DobbelenGame.NUM_DICES]));
			return actions;
		}
		else {
			List<DobbelenAction> actions = new LinkedList<DobbelenAction>();
			for (int i = 0; i <= DobbelenGame.NUM_DICES; i++) {
				generateActions(actions, extendedHistogram, i, 0);
			}
			if (DobbelenCondition.mustReThrow(this)) {
				DobbelenAction keepAllAction = new DobbelenAction(extendedHistogram);
				actions.remove(keepAllAction);
			}
			return actions;
		}
	}

	private void generateActions(List<DobbelenAction> actions, int[] histogram, int numKeepingDices, int startIndex) {
		int total = 0;
		for (int j = 0; j < histogram.length; j++) {
			total += histogram[j];
		}

		if (total == numKeepingDices) {
			actions.add(new DobbelenAction(histogram.clone()));
			return;
		}

		for (int j = startIndex; j < histogram.length; j++) {
			if (histogram[j] >= 1) {
				histogram[j]--;
				generateActions(actions, histogram, numKeepingDices, j);
				histogram[j]++;
			}
		}
	}

	public List<Dice> actionToDice(DobbelenAction action) {
		List<Dice> heldDices = new LinkedList<Dice>();
		int[] histogram = action.getHistogram();
		for (int i = 0; i < histogram.length; i++) {
			for (int j = 0; j < histogram[i]; j++) {
				heldDices.add(diceGroups.get(i).get(j));
			}
		}
		return heldDices;
	}

	public DobbelenState clone() {
		DobbelenState s = new DobbelenState();
		s.histogram = histogram.clone();
		s.index = index.clone();
		s.extendedHistogram = extendedHistogram.clone();
		s.diceGroups = new LinkedList<List<Dice>>();
		for (List<Dice> list : diceGroups) {
			List<Dice> clonedList = new LinkedList<Dice>();
			clonedList.addAll(list);
			s.diceGroups.add(clonedList);
		}
		return s;
	}

}
